
#include <iostream>
#include <sstream>
#include <vector>
#include <cassert>
#include <sys/time.h>
#include <pthread.h>

#include "../matrix.h"
#include "geomTypes.h"

typedef float                                   Number;
typedef Point<3, Number>                        Point3;
typedef Vector<3, Number>                       Vector3;
typedef MatrixT<Number>                         Matrix;

struct Segment3 {
   const Point3 operator[](int i) const { if (i==0) return s; else return t; }
   Point3 source() const { return s; };
   Point3 target() const { return t; };
   Point3 s;
   Point3 t;
};

#include "../core/tconstraints.h"
#include "../core/mlsDeformer.h"
#include "mlsDeformerFS.h"
#include "mlsRaw.h"

typedef TConstraints<Point3>  PConstraints;
PConstraints P, Q;
vector<Point3> positions, positionsIni;

void createMesh(vector<Point3>&, vector<Point3>&, uint, uint);
void createControlPoints(PConstraints&, PConstraints&, uint);
double get_clock();
void printInfoAndExit();

/// Implements the threads execution procedure 
void mlsThreads(unsigned, unsigned);
/// Computes the mls deformation into the GPU following a Fragment Shader scheme
void mlsGPU_fs(const vector<Point3>&, vector<Point3>&, const PConstraints&, const PConstraints&);

/// Defines a struct for threads processing
struct channel_t {
   uint   tid;       // channels identifier
   uint   ibegin;    // index begin
   uint   iend;      // index end
};

int main(int argc, char **argv) {
   
   if (argc !=5) if (argc !=6) printInfoAndExit();
   
   //~ cout<<"MLS time experiments (in sec.)... "<<endl;
   unsigned int meshWidth, meshHeight, NCTRLPOINTS, method, NUMTHREADS;
   meshWidth = atoi(argv[1]);
   meshHeight = atoi(argv[2]);
   NCTRLPOINTS = atoi(argv[3]);
   method = atoi(argv[4]);
   
   //~ cout<<"  meshWidth x meshHeight:NCTRLPOINTS: "<<meshWidth<<" x "<<meshHeight<<" : "<<NCTRLPOINTS<<" ";
   //~ if (method==0) cout<<" method: MLS serial raw ";
   //~ if (method==1) cout<<" method: MLS serial OO ";
   //~ if (method==2) cout<<" method: MLS GPU:FRAGMENT SHADER ";
   //~ if (method==3) cout<<" method: MLS multicore ";
   
   positions.resize(meshWidth*meshHeight);
   positionsIni.resize(meshWidth*meshHeight);
   
   createMesh(positions, positionsIni, meshWidth, meshHeight);
   createControlPoints(P, Q, NCTRLPOINTS);
   
   double t_start = get_clock();
   if (method==0) { /// MLS serial raw
      float p[3], pd[3];
      for (unsigned int i=0; i<positionsIni.size(); ++i) {
         p[0] = positionsIni[i][0];
         p[1] = positionsIni[i][1];
         p[2] = positionsIni[i][2];
         mlsEval(P.positions, Q.positions, p, pd);
         positions[i] = Point3(pd[0], pd[1], pd[2]);
      }
   }
   else if (method==1) { /// MLS serial OO
      typedef MlsDeformer<PConstraints>      MLSDeformer;
      MLSDeformer deformer;
      deformer.setSourceConstraints(P);
      deformer.setTargetConstraints(Q);
      for (unsigned int i=0; i<positionsIni.size(); ++i) {
         positions[i] = deformer.eval(positionsIni[i]);
      }
   }
   else if (method==2) { /// MLS GPU: fragment shader
      mlsGPU_fs(positionsIni, positions, P, Q);
   }
   else if (method==3) { /// MLS multicore
      assert(argc == 6);
      NUMTHREADS = atoi(argv[5]);
      //~ cout<<"  THREADS: "<<NUMTHREADS<<" ";
      mlsThreads(positionsIni.size(), NUMTHREADS);
   }
   else {
      cout<<"attention!!! method: invalid option!"<<endl;
      return 0;
   }
   
   double mlsTime = get_clock() - t_start;
   //~ cout<<"  mlsTime: "<<mlsTime<<" sec."<<endl;
   cout<<mlsTime<<endl;
   
   return 0;
}

void createMesh(vector<Point3> &pos, vector<Point3> &posIni, uint mesh_height, uint mesh_width) {
   
   assert(pos.size() == posIni.size());
   for (unsigned i=0; i<pos.size(); ++i) {
      const float x = rand()/(float)RAND_MAX;
      const float y = rand()/(float)RAND_MAX;
      const float z = rand()/(float)RAND_MAX;
      pos[i] = Point3(x,y,z);
   }
   posIni = pos;
}

void createControlPoints(PConstraints &P, PConstraints &Q, uint NCTRLPOINTS) {
   
   srand(time(NULL));
   for (unsigned i=0; i<NCTRLPOINTS; ++i) {
      {
      const float x = rand()/(float)RAND_MAX;
      const float y = rand()/(float)RAND_MAX;
      const float z = rand()/(float)RAND_MAX;
      P.push_back(Point3(x,y,z));
      }{
      const float x = rand()/(float)RAND_MAX;
      const float y = rand()/(float)RAND_MAX;
      const float z = rand()/(float)RAND_MAX;
      Q.push_back(Point3(x,y,z));
      }
   }
}

double get_clock() {
   
   struct timeval t;
   struct timezone tz;
   gettimeofday(&t,&tz);
   return (double) t.tv_sec + (double) t.tv_usec*1E-6;
}

void printInfoAndExit() {
   
   cout<<endl;
   cout<<"Usage: mlsSerial meshWidth[0..n] meshHeight[0..n] ncontrolPoints[0..n] method[0,1,2] {nthreads[0..n]}"<<endl;
   exit(0);
}

// ///////////////////////////////////

void mlsGPU_fs(const vector<Point3> &posIni, vector<Point3> &pos, 
                          const PConstraints &P, const PConstraints &Q) {
   
   // OpenGL context creation
   int argc=1; 
   glutInit(&argc, 0);
   GLuint winHandle = glutCreateWindow("noname");
   
   //~ MlsDeformerFS deformerfs(posIni, P);
   //~ deformerfs.execute(pos, Q);
   
   // OpenGL context destruction
   glutDestroyWindow(winHandle);
}

/// Implements the procedure executed by each thread
void *mls_t(void *ptr) {
   
   channel_t *ch;
   ch = (channel_t *)ptr;
   
   float p[3], pd[3];
   for (unsigned i=ch->ibegin; i<ch->iend; ++i) {
      p[0] = positionsIni[i][0];
      p[1] = positionsIni[i][1];
      p[2] = positionsIni[i][2];
      mlsEval(P.positions, Q.positions, p, pd);
      positions[i] = Point3(pd[0], pd[1], pd[2]);
   }
   pthread_exit(NULL);
}

void mlsThreads(unsigned nvertices, unsigned NUMTHREADS) {
   
   vector<pthread_t> threads;
   vector<channel_t> channels;
   
   threads.resize(NUMTHREADS);
   channels.resize(NUMTHREADS);
   
   uint blocksize = (unsigned)(ceil(nvertices/NUMTHREADS));
   uint ini = 0;
   uint end = blocksize;
   for (unsigned j=0; j<NUMTHREADS; ++j) {
      channels[j].tid = j;
      channels[j].ibegin = ini;
      channels[j].iend = (j==NUMTHREADS-1)? nvertices:end;
      
      pthread_create(&(threads[j]), NULL, mls_t, &(channels[j]));
      ini = end;
      end += blocksize;
   }
   for(unsigned j=0; j<NUMTHREADS; ++j)
      pthread_join(threads[j], NULL);
}
